﻿using Sustainalytics.ESGRatings.BusinessLogic.Computing.Utils;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sustainalytics.ESGRatings.BusinessLogic.Computing
{
    public class IndexByHeads<TU,TV> : IIndex<TU,TV>
    {
        readonly Dictionary<TU, int> _directIndex = new Dictionary<TU, int>();
        readonly List<TU> _reverseIndex = new List<TU>();
        readonly Dictionary<TU,TV> _keySelectorIndex= new Dictionary<TU, TV>();
        readonly Dictionary<TV,int> _selectors=new  Dictionary<TV, int>();
        int[] _headsIndices;

        private IndexByHeads()
        {
          
        }

        public static IndexByHeads<TU,TV> Create<T>(IEnumerable<T> items,Func<T,TU> keySelector, Func<T,TV> sortKeySelector,IComparer<TV> comparer)
        {
            
            var returnIndex = new IndexByHeads<TU, TV>();
            if (items == null)
                return returnIndex;

            int currentIndex = 0;
            int selectorIndex = -1;
            var heads = new List<int>();
            T previousItem = default(T);
            bool first = true;
            foreach (var item in items.OrderBy(sortKeySelector, comparer))
            {
                var currentSelection = sortKeySelector(item);
                if (!first)
                {
                    if (comparer.Compare(sortKeySelector(previousItem), currentSelection) != 0)
                    {
                        heads.Add(currentIndex);
                        returnIndex._selectors.Add(currentSelection,++selectorIndex);
                    }
                }
           
                first = false;
                TU projectedItem = keySelector(item);
                returnIndex._directIndex.Add(projectedItem, currentIndex);
                returnIndex._reverseIndex.Add(projectedItem);
                returnIndex._keySelectorIndex.Add(projectedItem,currentSelection);

                ++currentIndex;
                previousItem = item;
            }

            returnIndex._headsIndices = heads.ToArray();

            return returnIndex;
        }

        public int GetIndex(TU key)
        {
            int index;
            if (_directIndex.TryGetValue(key, out index))
            {
                return index;
            }
            return -1;
        }

        public int[] GetIndex(object p)
        {
            List<int> result = new List<int>();

            try
            {
                var selector = (TV)Convert.ChangeType(p, typeof(TV));
                int index = 0;
                foreach (var kvp in _keySelectorIndex)
                {
                    if (kvp.Value.Equals(selector))
                    {
                       result.Add(index);
                    }
                    index++;
                }


            }
            catch (InvalidCastException)
            {
                return new int[0];
            }
            return result.ToArray();
        }


        
        public TU GetItem(int index)
        {
            return _reverseIndex[index];
        }

        public int Count
        {
            get
            {
                return _reverseIndex.Count;
            }
        }

        public TU[] GetReverseIndexCopy() 
        {
            return _reverseIndex.ToArray();
        }

        public int GetGroupId(TV itemSelector)
        {
            int groupId;
            if (!_selectors.TryGetValue(itemSelector, out groupId))
            {
                groupId = -1;
            }
            return groupId;
        }

        public int[] GroupByHeads
        {
            get 
            {
                return _headsIndices;
            }
        }

        public int SelectorsCount { get { return _selectors.Count; } }

        public int[] GetIndex()
        {
            return _directIndex.Values.ToArray();
        }

        //TODO Madalina: test this method
        public IEnumerable<TU> GetGroupElements(IEnumerable<TV> itemSelector)
        {
            //return from kvp in _keySelectorList
            //       let company = kvp.Item1
            //       where itemSelector != null && company != null
            //       let found = itemSelector.Any()<TV>((TV)kvp.Item2)
            //       where found
            //       select kvp.Item1;
            //var enumerable = itemSelector as TV[] ?? itemSelector.ToArray();

            //return from item in _keySelectorList let item1 = item from selector in enumerable.Where(selector => selector.Equals(item1.Item2)) select item.Item1;
        throw  new NotImplementedException();
        
        }

        public int[] GetIndexis(IEnumerable<TV> itemSelector)
        {
            var keys = GetGroupElements(itemSelector);
            List<int> result =new List<int>();
            foreach (var key in keys)
            {
                result.AddRange(_directIndex.Where(x=>x.Key.Equals(key)).Select(x=>x.Value));
            }
            return result.ToArray();
        }

        public IEnumerable<TU> GetGroupElements(TU itemKey)
        {
            var heads = _headsIndices;
            return GetGroupElements(itemKey, heads);

        }

        private IEnumerable<TU> GetGroupElements(TU itemKey, IEnumerable<int> heads)
        {
            var companyIndex = GetIndex(itemKey);
            var upper = 0;
            var lower = 0;
            var boundedHeads = heads.Union(_directIndex.Count.Yield());


            foreach (var item in boundedHeads)
            {
                lower = upper;
                upper = item;
                if (lower <= companyIndex && companyIndex < upper)
                {
                    break;
                }
            }
            return Enumerable.Range(lower, upper - lower).Select(GetItem);
        }
     }


    //public class Index<T>
    //{
    //    Dictionary<T, int> _directIndex = new Dictionary<T, int>();
    //    List<T> _reverseIndex;

    //    public Index(IEnumerable<T> items)
    //    {
    //        if (items == null)
    //            return;

    //        int currentIndex = 0;
    //        List<T> reverseIndexList = new List<T>();
    //        foreach (var item in items)
    //        {
    //            this._directIndex.Add(item, currentIndex);
    //            reverseIndexList.Add(item);

    //            ++currentIndex;
    //        }

    //        this._reverseIndex = reverseIndexList;
    //    }

    //    public int GetIndex(T item)
    //    {
    //        return _directIndex[item];
    //    }

    //    public T GetItem(int index)
    //    {
    //        return _reverseIndex[index];
    //    }

    //    public int AddItem(T item)
    //    {
    //        int index = _reverseIndex.Count;
    //        _directIndex.Add(item, index);
    //        _reverseIndex.Add(item);
    //        return index;
    //    }

    //    public int Count {
    //        get
    //        {
    //            return _reverseIndex.Count;
    //        }
    //    }
    //}


   
}
